package article

import (
	"errors"
	"maki/search"
	"net/http"
	"strconv"

	"github.com/labstack/echo"

	"maki/model"
	"maki/utils"
)

// AddArticle 添加文章
func AddArticle(ctx echo.Context) error {
	data := new(addOrUpdateArticleBinding)
	if err := utils.BindAndValidate(ctx, data); err != nil {
		return err
	}
	v := ctx.Get("user")
	if v == nil {
		return utils.ErrUserNotLogin
	}
	user, _ := v.(*model.User)
	article := model.Article{
		UserID: user.ID,
	}
	utils.PartialUpdate(&article, data, "Tags")

	for _, tagName := range data.Tags {
		tag := model.Tag{Name: tagName}
		model.DB.FirstOrCreate(&tag, "name = ?", tagName)
		article.Tags = append(article.Tags, tag)
	}
	if status := model.DB.Create(&article); status.Error != nil {
		return status.Error
	}
	return ctx.JSON(http.StatusOK, utils.NewResult("success"))
}

// UpdateArticle 更新文章, 目前只能更新自己写的文章
func UpdateArticle(ctx echo.Context) error {
	data := new(addOrUpdateArticleBinding)
	if err := utils.BindAndValidate(ctx, data); err != nil {
		return err
	}
	ID, err := strconv.Atoi(ctx.Param("id"))
	if err != nil {
		return err
	}
	var oldArticle model.Article
	if model.DB.First(&oldArticle, ID).RecordNotFound() {
		return utils.ErrRecordNotFound
	}
	v := ctx.Get("user")
	user, _ := v.(*model.User)
	if oldArticle.UserID != user.ID {
		return errors.New("can't modify this article")
	}

	article := model.Article{
		UserID: user.ID,
	}
	utils.PartialUpdate(&article, data, "Tags")
	for _, tagName := range data.Tags {
		tag := model.Tag{Name: tagName}
		model.DB.FirstOrCreate(&tag, "name = ?", tagName)
		article.Tags = append(article.Tags, tag)
	}
	if err := model.DB.Model(&oldArticle).Association("Tags").
		Replace(article.Tags).Error; err != nil {
		return err
	}
	if status := model.DB.Model(&oldArticle).Omit("id").Updates(article); status.Error != nil {
		return status.Error
	}
	return ctx.JSON(http.StatusOK, utils.NewResult("success"))
}

// GetArticleList 获取文章列表
func GetArticleList(manage bool) func(echo.Context) error {
	return func(ctx echo.Context) error {
		var articles []model.Article
		querySet := model.DB.Model(&articles)
		result, err := utils.Paginate(ctx)
		if err != nil {
			return err
		}
		if !manage {
			querySet = querySet.Where("visible = ?", true)
		}
		tagName := ctx.QueryParam("tag")
		keyword := ctx.QueryParam("keyword")
		if tagName != "" {
			querySet = querySet.Joins("JOIN article_tag ON article_tag.article_id = articles.id").
				Joins("JOIN tags ON article_tag.tag_id = tags.id").Where("tags.name = ?", tagName)
		}
		if keyword != "" {
			docids := search.SearchDoc(keyword)
			querySet = querySet.Where("id in (?)", docids)
		}
		querySet.Count(&result.Total).Limit(result.Limit).Offset(result.Offset).Order("created_at DESC").
			Preload("User").Find(&articles)
		result.Data = articles
		return ctx.JSON(http.StatusOK, result)
	}
}

// GetArticle get single article by id
func GetArticle(mange bool) func(echo.Context) error {
	return func(ctx echo.Context) error {
		id, err := strconv.Atoi(ctx.Param("id"))
		if err != nil {
			return utils.NewParameterError("id")
		}
		var article model.Article
		querySet := model.DB.Model(&article)
		if !mange {
			querySet = querySet.Where("visible = ?", true)
		}
		if querySet.Preload("User").Preload("Comments.User").Preload("Tags").First(&article, id).RecordNotFound() {
			return utils.ErrRecordNotFound
		}
		return ctx.JSON(http.StatusOK, utils.NewResult(article))
	}
}

func DeleteArticle(ctx echo.Context) error {
	ID, err := strconv.Atoi(ctx.Param("id"))
	if err != nil {
		return err
	}
	var oldArticle model.Article
	if model.DB.First(&oldArticle, ID).RecordNotFound() {
		return utils.ErrRecordNotFound
	}
	v := ctx.Get("user")
	user, _ := v.(*model.User)
	if oldArticle.UserID != user.ID && !user.IsAdmin() {
		return errors.New("can't modify this article")
	}
	if status := model.DB.Delete(&oldArticle); status.Error != nil {
		return status.Error
	}
	return ctx.JSON(http.StatusOK, utils.NewResult("success"))
}

func ReBuildIndex(ctx echo.Context) error {
	go func() {
		var articles []model.Article
		var count int
		querySet := model.DB.Model(&articles)
		querySet.Count(&count)
		limit, offset := 100, 0
		for ; limit+offset < count+100; offset += 100 {
			querySet.Offset(offset).Limit(limit).Find(&articles)
			for _, article := range articles {
				article.AfterSave(nil)
			}
		}
		search.Engine.Flush()
	}()
	result := utils.NewResult("success")
	return ctx.JSON(http.StatusOK, result)
}
